概述
本节在前一节 TypeORM 动态连接多 MySQL 实例的基础上,进一步扩展到跨数据库类型的动态连接场景。通过在 AppService 中根据请求头 tenantId 返回不同数据库类型的配置(MySQL / PostgreSQL),实现同一个 TypeORM 模块访问多种数据库实例的能力。
PostgreSQL 数据库环境准备
Docker Compose 配置
为 PostgreSQL 创建独立的 Docker Compose 文件 docker-compose.postgres.yml:
# docker-compose.postgres.yml
services:
postgres:
image: postgres:15
container_name: postgres_db
environment:
POSTGRES_USER: pg_user
POSTGRES_PASSWORD: example
POSTGRES_DB: test_db
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
yaml
如果希望 MySQL 和 PostgreSQL 共享同一个 Adminer 管理界面,可以在其中一个 compose 文件中配置 Adminer,另一个文件仅定义数据库服务。注意避免 Adminer 端口 8080 冲突。
启动与验证
# 启动 PostgreSQL
docker compose -f docker-compose.postgres.yml up -d
# 验证容器运行状态
docker ps | grep postgres
bash
启动后通过 Adminer 或其他数据库客户端登录 PostgreSQL,确认 test_db 已成功创建。
安装 PostgreSQL 驱动
TypeORM 不内置数据库驱动,需要手动安装对应数据库的 Node.js 客户端:
# PostgreSQL 驱动
pnpm add pg
# MySQL 驱动(如尚未安装)
pnpm add mysql2
bash
Prisma 与 TypeORM 的运行机制不同:Prisma 内置了数据库驱动,无需额外安装;TypeORM 则依赖外部驱动包。这是两者在多数据库场景下的一个关键区别。
动态数据库配置实现
环境变量调整
在 .env 文件中添加 PostgreSQL 连接配置:
# 切换为 PostgreSQL 时使用
DB_TYPE=postgres
DB_PORT=5432
DB_USERNAME=pg_user
DB_PASSWORD=example
DB_DATABASE=test_db
env
Schema 同步
# 修改 .env 中的 DB_TYPE 为 postgres 后执行
pnpm typeorm sync
bash
同步成功后,PostgreSQL 数据库中将自动创建与 Entity 对应的表结构。
Service 层多数据库配置
在 AppService 中扩展 getDBConfig 方法,支持根据 tenantId 返回不同数据库类型的配置:
// app.service.ts
@Injectable()
export class AppService {
constructor(@Inject(REQUEST) private readonly request: Request) {}
getDBConfig(): any {
const tenantId = this.request.headers['tenant-id'] as string;
if (tenantId === 'mysql') {
return {
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test_db',
};
} else if (tenantId === 'postgres') {
return {
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'pg_user',
password: 'example',
database: 'test_db',
};
}
// 默认配置
return null;
}
}
typescript
App Module 配置合并
在 AppModule 中将动态配置与 .env 默认配置进行合并:
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService, appService: AppService) => {
const envConfig = configService.get('database');
const dynamicConfig = appService.getDBConfig();
// 注意:后面的对象会覆盖前面的同名属性
const finalConfig = Object.assign({}, envConfig, dynamicConfig);
return finalConfig;
},
inject: [ConfigService, AppService],
}),
],
})
export class AppModule {}
typescript
Object.assign中参数的顺序非常关键。Object.assign({}, envConfig, dynamicConfig)表示dynamicConfig会覆盖envConfig中的同名属性。如果顺序写反,会导致动态配置不生效。
测试验证
测试流程
- 确保 MySQL 和 PostgreSQL 都已启动并完成 schema 同步
- 在各自的数据库中插入测试数据
- 启动调试进程:
pnpm start:dev - 使用接口测试工具发送请求
测试用例
请求 MySQL 数据:
GET /hello
Headers:
tenant-id: mysql
http
响应内容来自 MySQL 数据库。
请求 PostgreSQL 数据:
GET /hello
Headers:
tenant-id: postgres
http
响应内容来自 PostgreSQL 数据库。
数据持久化提示
使用 docker compose 重新创建容器后,数据库中的数据会丢失。解决方法是在 compose 文件中配置 volume 映射:
volumes:
- ./data/mysql:/var/lib/mysql
yaml
这样容器内的数据会映射到宿主机,实现数据持久化。
架构总结
请求 -> Controller -> Service(根据 tenantId 返回配置)
|
TypeORM forRootAsync
|
┌──────────┴──────────┐
| |
DataSource 1 DataSource 2
(MySQL:3306) (PostgreSQL:5432)
text
TypeORM forRootAsync 的工厂函数模式提供了以下能力:
- 复用同一个 TypeORM 模块,通过工厂函数动态产生不同的 DataSource 实例
- 多 Client 实例管理,可以将不同的实例注入到对应的 Repository 中
- 业务逻辑封装,所有数据库切换逻辑集中在 Service 层,Controller 无需关心具体使用的数据库类型
后续开发中,应将
AppService中的数据库配置逻辑重构到database/typeorm/目录下独立的 TypeORM Service 中,以便与业务代码分离。
↑